
/**
 * This class will define the service aspect of VoyagerConnect.
 */
package com.gtosoft.voyager;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.gtosoft.libvoyager.autosession.AutoSessionAdapter;
import com.gtosoft.libvoyager.db.DashDB;
import com.gtosoft.libvoyager.util.EasyTime;
import com.gtosoft.libvoyager.util.EventCallback;
import com.gtosoft.libvoyager.util.GeneralStats;
import com.gtosoft.libvoyager.util.OOBMessageTypes;

/**
 * @author Brad Hein / GTOSoft LLC. 
 */
public class VoyagerService extends Service {
	
	// if true, lots of debug messages may be produced for development and testing purposes. 
	private boolean DEBUG = false;

	AutoSessionAdapter mSessionAdapter;
	
	boolean mThreadsOn = true;
	
	DashDB ddb;
//	HybridSession hs;
	GeneralStats mgStats;
	
	Thread mtDataCollector = null;
	String mBTPeerMAC;
//	ServiceHelper msHelper;
	
	// for posting stuff back to the main service thread. 
	Handler mHandler = new Handler();

	// stuff for notificaitons. 
	NotificationManager mNotificationManager = null;
	private static final int VOYAGER_NOTIFY_ID_OBD = 1234;
	private static final int VOYAGER_NOTIFY_ID_CHECKENG = 2345;

	Notification mNotificationGeneral = null;
	Notification mNotificationCheckEngine = null;



	/**
	 * Constructor.
	 */
	public VoyagerService() {
		// TODO Auto-generated constructor stub
	}


	/**
	 * Create method. Executed when the service is created but not yet started. 
	 */
	@Override
	public void onCreate() {
		super.onCreate();
		if (mgStats == null) mgStats = new GeneralStats();
	}

	
	/**
	 * This method is executed once the service is "running". This is where magic happens. 
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {

		boolean ret = doStartupSequence();
		
		if (ret == false) {
			msg ("Ignored EXTRA REQUEST for service to start!");
		}
		
		// ask that the system re-deliver the start request with intent if we die.
		return START_REDELIVER_INTENT;
	}

	private synchronized boolean doStartupSequence () {
		if (mSessionAdapter != null)
			return false;
		
//		setCurrentStateMessage("Adapter init...");
		
		mSessionAdapter = new AutoSessionAdapter(VoyagerService.this, BluetoothAdapter.getDefaultAdapter(), mLocalOOBMessageHandler, mLocalDPNArrivedHandler);
//		setCurrentStateMessage("Adapter is up");
		
		// just prints stats. 
		startDataCollectorLoop();

		return true;
	}
	
	
	/**
	 * Sends a message through the OOB pipe. 
	 * @param dataName
	 * @param dataValue
	 */
	private void sendOOBMessage (String dataName, String dataValue) {
		if (mLocalOOBMessageHandler == null)
			return;
		mLocalOOBMessageHandler.onOOBDataArrived(dataName, dataValue);
	}

	/**
	 * Shutdown sequence. Close out any open connections. 
	 */
	@Override
	public void onDestroy() {
		sendOOBMessage(OOBMessageTypes.SERVICE_STATE_CHANGE,"shutdown");
		EasyTime.safeSleep(500);
		super.onDestroy();
		shutdown();
	}


	/**
	 * Just prints stats for now? Maybe not even necessary in release version. 
	 * @return
	 */
    private boolean startDataCollectorLoop () {
    	if (mtDataCollector != null) {
    		return false;
    	}
    	
    	// Define the thread. 
    	mtDataCollector = new Thread() {
    		public void run () {
    			while (mThreadsOn == true) {
    				mgStats.incrementStat("dataCollectorLoops");
    				// moved this to the top of the loop so that we can run a "continue" and not cause a tight loop. 
    				EasyTime.safeSleep(10000);
    				
    				if (mSessionAdapter != null) {
    					// svc.hs.rScan.loops=1465
    					// updateOBDNotification("svc.hs.rScan.loops=" + getStats().getStat("svc.hs.rScan.loops"));
    					if (DEBUG) dumpStatsToScreen();
    				}
    				
    			}// end of main while loop. 
    			msg ("Data collector loop finished.");
    		}// end of run().
    	};// end of thread definition. 
    	
    	// kick off the thread.
    	mtDataCollector.start();
    	
    	return true;
    }

    /**
     * write allDPNsAsString to the screen. 
     */
	private void dumpAllDPNsToScreen() {
		try {
			msg(mSessionAdapter.getHybridSession().getPIDDecoder().getAllDataPointsAsString());
		} catch (Exception e) {
		}
	}

	private void dumpStatsToScreen  () {
		final String _stats = getStats().getAllStats();
		msg (_stats);
	}

	public GeneralStats getStats () {
		if (mSessionAdapter != null) mgStats.merge("svc", mSessionAdapter.getStats());
		
		return mgStats;
	}

	
    
    // Defines the logic to take place when an out of band message is generated by the hybrid session layer.
	EventCallback mLocalOOBMessageHandler = new EventCallback () {

		@Override
		public void onOOBDataArrived(String dataName, String dataValue) {
			
			if (mThreadsOn != true) {
				msg ("Ignoring OOB message out of scope. Threads are off. " + dataName + "=" + dataValue);
				return;
			}
			
			msg ("OOB Data: " + dataName + "=" + dataValue);
			
			// state change?
			if (dataName.equals(OOBMessageTypes.IO_STATE_CHANGE)) {
				int newState = 0;
				try {
					newState = Integer.valueOf(dataValue);
					msg ("IO State changed to " + newState);
				} catch (NumberFormatException e) {
					msg ("ERROR: Could not interpret new state as string: " + dataValue);
				}
				
			}// end of "if this was a io state change". 
		}// end of session state change handler. 
	};// end of override.


	// TODO: Register this somewhere? 
	EventCallback mLocalDPNArrivedHandler = new EventCallback () {
		@Override
		public void onDPArrived(String DPN, String sDecodedData, int iDecodedData) {
//			msg ("DPN Arrived: " + DPN + "=" + sDecodedData);
		}// end of onDPArrived. 
	};// end of eventcallback def. 

    


	/**
	 * Required when defining a service. We won't be using IPC/binding at this point so it will go unused for now. 
	 */
	@Override
	public IBinder onBind(Intent intent) {
		msg ("Warning: onBind() executed but we don't support binding at this time.");
		return null;
	}
	
	private void msg (String m) {
		Log.d("VoyagerService",m);
	}

	
	
	/**
	 * Wrapper function that can be called from any thread, runs the updater in the main thread. 
	 * @param newText - the text we shall post to the notification. 
	 * @return - returns true on success, false on failure.
	 */
	private boolean updateOBDNotification (String newText) {
		
		final String txt = newText;

		//vdb.logDBMessage("updateOBDNotification(): New Notification Message: " + newText);
		
		mHandler.post(new Runnable () {
			public void run () {
				updateOBDNotification_ui(txt);
			} // end of run() definition. 
		}// end of handler post. 
		); // end of mhandler.post call. 
		
		return true;		
	}
	
	/**
	 * Provide a means for member functions to update the system notifcation text. 
	 * @param newText
	 * @return
	 */
	private boolean updateOBDNotification_ui (String newText) {

		// initialize stuff if necessary. 
		if (mNotificationManager == null) {
			String ns = Context.NOTIFICATION_SERVICE;
			mNotificationManager = (NotificationManager) getSystemService(ns);
		}
		
		if (mNotificationGeneral == null) {
			CharSequence tickerText = "Voyager Connect";
			
			long when = System.currentTimeMillis();
			
			mNotificationGeneral = new Notification (R.drawable.voyagercarlogo,tickerText, when);
			mNotificationGeneral.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
		}
			
		Intent i = new Intent (this,ConnectUI.class);
		PendingIntent pi = PendingIntent.getActivity (this,0,i,0);
	
		mNotificationGeneral.setLatestEventInfo(getApplicationContext(),getString(R.string.app_name), newText, pi);		
		mNotificationManager.notify (VOYAGER_NOTIFY_ID_OBD,mNotificationGeneral);
		
		return true;
	}
	
	/**
	 * Put up a notification that there are DTC's available. 
	 * @return
	 */
	private boolean notifyCheckEngine (String infoText) {
		
		// initial config stuff. 
		if (mNotificationManager == null) {
			String ns = Context.NOTIFICATION_SERVICE;
			mNotificationManager = (NotificationManager) getSystemService(ns);
		}
		
		if (mNotificationCheckEngine == null) {
			CharSequence tickerText = "Check Engine (click for details)";
			
			long when = System.currentTimeMillis();
			mNotificationCheckEngine = new Notification (R.drawable.checkengine2,tickerText, when);
			mNotificationCheckEngine.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
		}
		
//		Intent i = new Intent (this,TroubleActivity.class);
//		PendingIntent pi = PendingIntent.getActivity (this,0,i,0);
//		mNotificationCheckEngine.setLatestEventInfo(getApplicationContext(),"Check Engine", infoText, pi);
		mNotificationManager.notify (VOYAGER_NOTIFY_ID_CHECKENG,mNotificationCheckEngine);

		
		return true;
	}

		
	/**
	 * call this method to shut down. 
	 */
	private void shutdown () {
		mThreadsOn = false;
		// shut down the hybridsession chain.
//		if (hs != null) hs.shutdown();
		if (mSessionAdapter != null) mSessionAdapter.shutdown();
		// interrupt the data collection thread. 
		if (mtDataCollector != null) mtDataCollector.interrupt();
		stopSelf();
	}
	
}
